package com.imis.base.util;

import cn.hutool.core.io.FileUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.google.code.kaptcha.Producer;
import com.imis.base.constant.CommonConstant;
import com.imis.base.constant.enums.ArgumentResponseEnum;
import com.imis.base.constant.enums.VerificationCodeTypeEnum;
import com.imis.module.api.model.vo.VerificationCodeVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Random;

/**
 * <p>
 * KaptchaVerificationUtil<br>
 * 验证码操作类
 * </p>
 *
 * @author XinLau
 * @version 1.0
 * @since 2020年03月31日 11:20
 */
@Slf4j
@Component
public class KaptchaVerificationUtil {

    /**
     * 滑块验证码 - _模板图片文件
     */
    @Value(value = "${imis-boot.path.Kaptcha.templateImagePath}")
    private String templateImagePath;

    /**
     * 滑块验证码 - 描边图片文件
     */
    @Value(value = "${imis-boot.path.Kaptcha.borderImagePath}")
    private String borderImagePath;

    /**
     * 滑块验证码 - 源图片文件
     */
    @Value(value = "${imis-boot.path.Kaptcha.sourceImagePath}")
    private String sourceImagePath;

    /**
     * 这里的 getDefaultKapcha 要和 KaptchaConfig 里面的bean命名一样
     */
    @Resource(name = "charVerificationCode")
    private com.google.code.kaptcha.Producer charVerificationCode;

    @Resource(name = "operationVerificationCode")
    private com.google.code.kaptcha.Producer operationVerificationCode;

    /**
     * Redis 工具类
     */
    private RedisUtil redisUtil;

    @Autowired
    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    /**
     * 生成验证码图片
     *
     * @param verificationCode - 验证码对象
     * @param text             - 验证码内容
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/4/10 13:44
     */
    private void createImage(Producer producer, VerificationCodeVO verificationCode, String text) throws IOException {
        // 1.生成验证码图片
        BufferedImage bufferedImage = producer.createImage(text);
        // 2.设置ByteArrayOutputStream
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 3.生成图片
        ImageIO.write(bufferedImage, "png", byteArrayOutputStream);
        // 4.图片字节数组
        byte[] bytes = byteArrayOutputStream.toByteArray();
        // 5.Base64加密
        verificationCode.setVerificationCode(org.springframework.util.Base64Utils.encodeToString(bytes));
    }

    /**
     * 获取字符验证码
     *
     * @return VerificationCodeVO - 验证码返回值 对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 11:37
     */
    private VerificationCodeVO getCharVerificationCode() {
        String verificationCodeIdentification = ConvertUtils.randomGen(CommonConstant.VERIFICATION_CODE_IDENTIFICATION_LENGTH);
        VerificationCodeVO verificationCode = new VerificationCodeVO();
        verificationCode.setType(VerificationCodeTypeEnum.CHAR.name());
        verificationCode.setVerificationCodeIdentification(verificationCodeIdentification);
        try {
            // 1.生成字符验证码文本
            String text = charVerificationCode.createText();
            // 2.设置Redis缓存
            Boolean setRedis = doVerificationCodeCache(text, verificationCode.getType(), verificationCodeIdentification);
            if (!setRedis) {
                return verificationCode;
            }
            // 3.生成字符验证码图片
            createImage(charVerificationCode, verificationCode, text);
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
        return verificationCode;
    }

    /**
     * 获取运算验证码
     *
     * @return VerificationCodeVO - 验证码返回值 对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 11:37
     */
    private VerificationCodeVO getOperationVerificationCode() {
        String verificationCodeIdentification = ConvertUtils.randomGen(CommonConstant.VERIFICATION_CODE_IDENTIFICATION_LENGTH);
        VerificationCodeVO verificationCode = new VerificationCodeVO();
        verificationCode.setType(VerificationCodeTypeEnum.OPERATION.name());
        verificationCode.setVerificationCodeIdentification(verificationCodeIdentification);
        try {
            // 1.生成运算验证码文本
            String text = operationVerificationCode.createText();
            // 2.截取运算验证码文本
            String value = text.substring(0, text.lastIndexOf(StringPool.AT));
            // 3.截取计算结果
            String result = text.substring(text.lastIndexOf(StringPool.AT) + 1);
            // 4.设置Redis缓存
            Boolean setRedis = doVerificationCodeCache(result, verificationCode.getType(), verificationCodeIdentification);
            if (!setRedis) {
                return null;
            }
            // 5.生成字符验证码图片
            createImage(operationVerificationCode, verificationCode, value);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
        return verificationCode;
    }

    /**
     * 获取滑块验证码
     *
     * @return VerificationCodeVO - 验证码返回值 对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 11:37
     */
    private VerificationCodeVO getSlideVerificationCode() {
        String verificationCodeIdentification = ConvertUtils.randomGen(CommonConstant.VERIFICATION_CODE_IDENTIFICATION_LENGTH);
        VerificationCodeVO verificationCode = new VerificationCodeVO();
        verificationCode.setType(VerificationCodeTypeEnum.SLIDE.name());
        verificationCode.setVerificationCodeIdentification(verificationCodeIdentification);
        try {
            // 1.获取所有滑动验证码源文件（支持文件夹中的文件夹）
            List<File> kaptchSourceImageFileList = FileUtil.loopFiles(sourceImagePath);
            ArgumentResponseEnum.VERIFICATION_CODE_ERR.assertNotEmpty(kaptchSourceImageFileList);
            // 3.随机获取一个滑动验证码源文件
            Random random = new Random(System.currentTimeMillis());
            File kaptchSourceImageFile = kaptchSourceImageFileList.get(random.nextInt(kaptchSourceImageFileList.size()));
            // 4.获取模板图片文件 与 描边图片文件
            File templateImageFile = FileUtil.file(templateImagePath);
            File borderImageFile = FileUtil.file(borderImagePath);
            // 5.获取图片文件的后缀名 获取模板图文件类型
            String templateImageFileExtName = FileUtils.extName(templateImageFile);
            String templateImageFileType = templateImageFile.getName().substring(templateImageFile.getName().lastIndexOf(StringPool.DOT) + 1);
            // 6.读取图片文件
            BufferedImage kaptchSourceImage = ImageIO.read(kaptchSourceImageFile);
            BufferedImage templateImage = ImageIO.read(templateImageFile);
            BufferedImage borderImage = ImageIO.read(borderImageFile);
            // 7.获取原图感兴趣区域坐标
            SlideKaptchaCreatorUtil.generateCutoutCoordinates(verificationCode, kaptchSourceImage, templateImage);
            // 8.设置坐标Redis缓存
            Integer x = verificationCode.getX(), y = verificationCode.getY();
            doVerificationCodeCache(x + CommonConstant.COMMA + y, verificationCode.getType(), verificationCodeIdentification);
            // 9.根据原图生成遮罩图和切块图
            SlideKaptchaCreatorUtil.pictureTemplateCutout(kaptchSourceImageFile, templateImageFile, verificationCode);
            // 10.剪切图描边
            SlideKaptchaCreatorUtil.cutoutImageEdge(verificationCode, borderImage, FileUtils.extName(borderImageFile));
        } catch (IOException e) {
            log.debug(e.getMessage(), e);
            ArgumentResponseEnum.VERIFICATION_CODE_ERR.assertFail(e);
        }
        // 11.返回
        return verificationCode;
    }

    /**
     * 设置验证码缓存
     *
     * @param content                        - 验证码缓存 对象
     * @param type                           - 验证码类型type: char - 字符验证码；operation - 运算验证码；slide - 滑块验证码
     * @param verificationCodeIdentification - 验证码标识
     * @return Boolean -
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 12:01
     */
    private Boolean doVerificationCodeCache(String content, String type, String verificationCodeIdentification) {
        // 设置Redis缓存，Key为缓存前缀加验证码类型加用户IP，有效时间2分钟，180秒
        String key = CommonConstant.KAPTCHA_SESSION_KEY + CommonConstant.UNDERSCORE + type + CommonConstant.UNDERSCORE + verificationCodeIdentification;
        return redisUtil.set(key, content, CommonConstant.KAPTCHA_SESSION_EFFECTIVE_TIME);
    }

    /**
     * 获取验证码
     *
     * @param type - 验证码类型type: char - 字符验证码；operation - 运算验证码；slide - 滑块验证码
     * @return VerificationCodeVO - 验证码返回值 对象
     * @author XinLau
     * @creed The only constant is change ! ! !
     * @since 2020/3/30 12:01
     */
    public VerificationCodeVO getVerificationCode(String type) {
        if (ConvertUtils.isEmpty(type)) {
            type = VerificationCodeTypeEnum.OPERATION.name();
        }
        VerificationCodeTypeEnum verificationCodeType = Enum.valueOf(VerificationCodeTypeEnum.class, type.toUpperCase());
        VerificationCodeVO verificationCodeVO;
        // 获取验证码生成类型     char - 字符验证码；operation - 运算验证码；slide - 滑块验证码
        log.debug(type);
        switch (verificationCodeType) {
            // 字符验证码
            case CHAR:
                verificationCodeVO = getCharVerificationCode();
                break;
            // 滑块验证码
            case SLIDE:
                verificationCodeVO = getSlideVerificationCode();
                break;
            // 运算验证码
            default:
                verificationCodeVO = getOperationVerificationCode();
        }
        return verificationCodeVO;
    }

}
